
#include "../common/mem.h"
#include "../common/error.h"
#include "../common/dynamic_ptr_arr.h"
#include <memory.h>

typedef struct _additional_alloc_info
{
	int index;
	int size;
} additional_alloc_info;

static dynamic_ptr_arr* _mem_allocs = 0;
static int _in_mem_module = 0;

void free_all()
{
	additional_alloc_info* block_info_ptr;
	int i;

	/*  Nothing allocated? just return */
	if (_mem_allocs == 0)
		return;

	for (i = 0; i < _mem_allocs->count; i++)
	{
		block_info_ptr = (additional_alloc_info*) _mem_allocs->values[i];
		free(block_info_ptr);
	}

	_mem_allocs->count = 0;
}

void register_allocated(additional_alloc_info* block)
{
	if (_mem_allocs == 0)
	{
		_in_mem_module = 1; /*  "dynamic_ptr_arr__add" might cause allocation */
		_mem_allocs = dynamic_ptr_arr__allocate_capacity(256);
		_in_mem_module = 0;
	}

	block->index = _mem_allocs->count;

	_in_mem_module = 1; /*  "dynamic_ptr_arr__add" might cause allocation */
	dynamic_ptr_arr__add(_mem_allocs, block);
	_in_mem_module = 0;
}

void* mem_alloc(int amount, int element_size)
{
	additional_alloc_info* block;

	if (_in_mem_module)
		return malloc(amount * element_size);

	block = (additional_alloc_info*) malloc(
		sizeof(additional_alloc_info) + amount * element_size);

	if (block != 0)
	{
		block->size = amount * element_size;
		register_allocated(block);
		block++;
	}

	return block;
}

void* mem_alloc_and_zero(int amount, int element_size)
{
	additional_alloc_info* block;

	if (_in_mem_module)
		return calloc(amount, element_size);

	block = (additional_alloc_info*) calloc(
		1, sizeof(additional_alloc_info) + amount * element_size);

	if (block != 0)
	{
		block->size = amount * element_size;
		register_allocated(block);
		block++;
	}

	return block;
}

void mem_free(void* block)
{
	additional_alloc_info info;
	additional_alloc_info* block_info_ptr;
	additional_alloc_info* swapped_block_info;

	if (block == 0)
		return;

	if (_in_mem_module)
	{
		free(block);
		return;
	}

	block_info_ptr = (additional_alloc_info*) block;
	block_info_ptr--;

	info = *block_info_ptr;

	free(block_info_ptr);

	/*  Remove the allocated block from the array */
	_mem_allocs->count--;

	/*  Not the last allocated block? */
	if (info.index != _mem_allocs->count)
	{
		swapped_block_info = (additional_alloc_info*)_mem_allocs->values[_mem_allocs->count];
		swapped_block_info->index = info.index;
		_mem_allocs->values[info.index] = _mem_allocs->values[_mem_allocs->count];
	}
}

void* mem_realloc(void* block, int amount, int element_size)
{
	additional_alloc_info* old_block;
	additional_alloc_info* new_block;
	additional_alloc_info block_info;

	if (_in_mem_module)
		return realloc(block, amount * element_size);

	old_block = block;
	old_block--;

	block_info = *old_block;

	new_block = (additional_alloc_info*) realloc(old_block,
		sizeof(additional_alloc_info) + amount * element_size);

	if (new_block != 0)
	{
		_mem_allocs->values[block_info.index] = new_block;
		new_block++;
	}

	return new_block;
}

/*  Only for "managed" blocks!!! */
void mem_replace_with_clone(void** block)
{
	void* old_block = *block;
	*block = mem_clone(old_block);
	mem_free(old_block);
}

/*  Only for "managed" blocks!!! */
void* mem_clone(void* block)
{
	additional_alloc_info* old_block;
	void* new_block;

	old_block = (additional_alloc_info*) block;
	old_block--;
	new_block = mem_alloc(1, old_block->size);

	memcpy(new_block, block, old_block->size);

	return new_block;
}
